#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <netlink/socket.h>
#include <signal.h>
#include <stdint.h>
#include "vtl.h"
#include "fileops.h"

/* Prototypes */
static int send_msg(void *buff, unsigned int len, int attr);
static inline int send_buffer_msg(void *buff, unsigned int len);
static inline int send_param_msg(uint32_t param);

/*
 * Generic macros for dealing with netlink sockets. Might be duplicated
 * elsewhere. It is recommended that commercial grade applications use
 * libnl or libnetlink and use the interfaces provided by the library
 */
#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
#define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN)
#define NLA_DATA(na) ((void *)((char*)(na) + NLA_HDRLEN))

#define ERR_ON(exp, msg...)  \
  do { if (exp) { printf(msg); goto err; } } while (0)

/* Global Variables */
FILE *curTape;
int done = 0;
struct nl_handle *sock;
int family;

static inline int send_buffer_msg(void *buff, unsigned int len);

// enum for the attributes

enum {
    VTL_A_UNSPEC,
    VTL_A_BUFFER_SIZE,
    VTL_A_BUFFER,
    VTL_A_CREATE_TAPE,
    VTL_A_REWIND_TAPE,
    VTL_A_REPLACE_TAPE,
    VTL_A_DELETE_TAPE,
    __VTL_A_MAX,
};
#define VTL_A_MAX (__VTL_A_MAX - 1)

// struct nla_policy for setting the types of the attributes
static struct nla_policy VTL_genl_policy[VTL_A_MAX + 1] = {
    [VTL_A_BUFFER_SIZE] =
    { .type = NLA_U32},
    [VTL_A_BUFFER] =
    { .type = NLA_UNSPEC},
    [VTL_A_CREATE_TAPE] =
    { .type = NLA_U32},
    [VTL_A_REWIND_TAPE] =
    { .type = NLA_U32},
    [VTL_A_REPLACE_TAPE] =
    { .type = NLA_U32},
    [VTL_A_DELETE_TAPE] =
    { .type = NLA_U32},
};

/*
 * Callback function for recv()ing messages.
 */
static int parse_cb(struct nl_msg *msg, void *arg) {
    struct nlmsghdr *nlh = nlmsg_hdr(msg);
    struct nlattr * attrs[VTL_A_MAX + 1];

    // Validate message and parse attributes
    genlmsg_parse(nlh, 0, attrs, VTL_A_MAX + 1, VTL_genl_policy);

    // Handling a READ command
    if (attrs[VTL_A_BUFFER_SIZE] != 0) {
        printf("Got a read message");
        int rc = 0;
        int len = *((int*) nla_data(attrs[VTL_A_BUFFER_SIZE]));
        printf(" with param %d\n", len);
        char *buf = (char*) malloc(len);
        rc = do_read(buf, len);
        if (rc < 0) return send_buffer_msg("\0", 0);
        else return send_buffer_msg(buf, rc);
    }

    // Handling a WRITE message
    if (attrs[VTL_A_BUFFER] != 0) {
        printf("Got a write message ");
        int rc = 0;
        int len = nla_len(attrs[VTL_A_BUFFER]);
        printf("of length %d\n", len);
        return send_param_msg(do_write(nla_data(attrs[VTL_A_BUFFER]), len));
    }

    // Handling a create-tape command
    if (attrs[VTL_A_CREATE_TAPE] != 0) {
        printf("Got a create-tape message\n");
        return send_param_msg(create_tape());
    }

    // REWIND_TAPE
    if (attrs[VTL_A_REWIND_TAPE] != 0) {
        printf("Got a rewind-tape message\n");
        return send_param_msg(rewind_current_tape());
    }

    // REPLACE TAPE
    if (attrs[VTL_A_REPLACE_TAPE] != 0) {
        printf("Got a replace-tape message with param %d\n", *((uint32_t*) nla_data(attrs[VTL_A_REPLACE_TAPE])));
        return send_param_msg(replace_tape(*((uint32_t*) nla_data(attrs[VTL_A_REPLACE_TAPE]))));
    }

    // DELETE TAPE
    if (attrs[VTL_A_DELETE_TAPE] != 0) {
        printf("Got a delete-tape message with param %d\n", *((uint32_t*) nla_data(attrs[VTL_A_DELETE_TAPE])));
        return send_param_msg(delete_tape(*((uint32_t*) nla_data(attrs[VTL_A_DELETE_TAPE]))));
    }

    return NL_OK;
}

int do_read(char *buf, int len) {
    return read_current_tape(buf, len);
}

int do_write(char *buf, int len) {
    return write_current_tape(buf, len);
}


//Sends a message using the STD_ATTR attribute to our family
//(That is a standard message).
//For more info see send_msg.
//Return send_msg returns.

static inline int send_buffer_msg(void *buff, unsigned int len) {
    return send_msg(buff, len, VTL_A_BUFFER);
}

//Sends a message using the FIN_ATTR attribute to our family
//(Signaling that the daemon should exit).
//For more info see send_msg.
//Return send_msg returns.

static inline int send_param_msg(uint32_t param) {
    printf("Sending message: param %d\n", param);
    if (0 < send_msg(0, param, VTL_A_BUFFER_SIZE))
        return 0;
    return 1;
}

//Sends a message using the attr attribute to our family.
//The message will be NLM_F_MULTI - termination can be done by hand.
//Return 0 on success.
//Returns -ENOMEM if not enough mem.
//Returns -EMSGSIZE if not enough room in the message to store the buffer.
//(This might happen because of nla_put).

static int send_msg(void *buff, uint32_t len, int attr) {
    struct nl_msg *msg;
    void *vrv = 0;
    int rc;
    msg = nlmsg_alloc();

    ERR_ON(msg == NULL, "Error allocating message.\n");


    vrv = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_MULTI,
            attr, 1); //attr == cmd because they are in the same order

    ERR_ON(vrv == NULL, "Error - genlmsg returned NULL!\n");

    if (buff != 0) {
        rc = nla_put(msg, attr, len, buff);
    } else {//if buff==0 then this is a param send
        rc = nla_put_u32(msg, attr, len);
    }
    if (rc != 0)
        goto msg_err;

    rc = nl_send_auto_complete(sock, msg);

    ERR_ON(rc < 0, "Error sending the message (%d).\n", rc);


err:
    msg_err :
    if (msg == NULL)
        goto end;

    nlmsg_free(msg);
end:
    return rc;
}

int my_select(int sock) {
    fd_set readfds;
    struct timeval timeout;
    int status = 0;

    FD_ZERO(&readfds);
    FD_SET(sock, &readfds);
    timeout.tv_sec = 20;
    timeout.tv_usec = 0;
    for (;;) {
        if (-1 == (status = select(sock + 1,
                &readfds, NULL, NULL, &timeout))) {
            if (EINTR == errno) continue; /* signal interrupted, ignore */
            perror("select()");
        } else {
            if (0 == status) return status; /* if timed-out, return 0 */
            if (!FD_ISSET(sock, &readfds)) return -1; /* if it's not SET then something's wrong */
            return status; /* if we got till here, then everything is ok */
        }
    }
}

void termhandler(int s) {
    printf("Caught a SIGTERM... shutting down.\n");
    shutting_down(); //zip the current tape, if we're in zip mode
    exit(0);
}

int main(int argc, char **argv) {
    struct nl_msg *msg;
    int rv = 0;
    void *vrv = 0;
    int sockfd;

    if (argc < 2) {
        printf("Wrong usage! ./daemon <tapes-dir>\n");
        return -1;
    }

    chdir(argv[1]);

    daemon(1,0);

    signal(SIGTERM, termhandler);

    
    investigate_tape_lib();
    // Allocate a new netlink socket
    sock = nl_handle_alloc();
    ERR_ON(sock == NULL, "Failed allocating socket.\n");

    //Disable sequence checks.

    nl_disable_sequence_check(sock);
    //Set the local port
    nl_socket_set_local_port(sock, getpid());

    // Connect to generic netlink socket on kernel side
    rv = genl_connect(sock);

    ERR_ON(rv, "Error connecting the socket (%d).\n", rv);

    // Ask kernel to resolve family name to family id
    family = genl_ctrl_resolve(sock, "VTL_GENL");

    ERR_ON(family < 0, "Error resolving family (%d).\n", family);

    // Construct a generic netlink by allocating a new message, fill in
    // the header and append a simple integer attribute.
    msg = nlmsg_alloc();

    ERR_ON(msg == NULL, "Error allocating message.\n");

    vrv = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_ECHO,
            1, 1);

    ERR_ON(vrv == NULL, "Error - genlmsg returned NULL!\n");

    rv = nla_put(msg, 1, strlen("Testing") + 1, "Testing\0");
    ERR_ON(rv < 0, "Error - nla_put_string returned negative result!\n");

    printf("DAEMON\n------\nSending an initialization message...\n");
    send_buffer_msg("212", 3);
    ERR_ON(rv < 0, "Error sending the message (%d).\n", rv);

    // Free message
    nlmsg_free(msg);

    nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, parse_cb, NULL);

    sockfd = nl_socket_get_fd(sock);
    while (1) {
        if (0 < my_select(sockfd))
            nl_recvmsgs_default(sock);
    }

    return 0;
err:
    return -1;
}
